<?php

declare(strict_types=1);

namespace Zlf\AppValidate\Rule;

/**
 * 身份证号码验证类
 * 用于验证输入值是否为有效的中国身份证号码。
 */
class IdentityNumberValidate extends RuleAbstract
{
    /**
     * 默认错误消息
     * 当验证失败时，返回的错误信息模板。
     * @var string
     */
    protected $_error = '{label}错误';

    /**
     * 验证身份证号码
     * 使用正则表达式和校验算法验证输入值是否为有效的身份证号码。
     * 如果输入值为空且 `_noSkipping` 属性为 `true`，则直接返回 `true`。
     *
     * @param mixed $value 输入值
     * @param string $label 验证名（用于错误信息中）
     * @return bool 验证结果
     */
    public function validate($value, string $label = ''): bool
    {
        // 确保输入值是字符串类型
        if (is_string($value)) {
            // 如果值为空且 `_noSkipping` 为 `true`，直接返回 `true`
            if (empty($value) && $this->_noSkipping === true) {
                $this->_value = $value;
                return true;
            }
            // 调用内部方法验证身份证号码
            if ($this->verifyIdCard($value)) {
                $this->_value = $value;
                return true;
            }
        }
        // 如果验证失败，添加错误信息并返回 `false`
        $this->addError($this->_error, $label);
        return false;
    }

    /**
     * 验证身份证号码的格式和校验位
     * 1. 使用正则表达式验证身份证号码的格式是否正确。
     * 2. 验证身份证号码的最后一位校验位是否正确。
     *
     * @param string $value 身份证号码
     * @return bool 验证结果
     */
    private function verifyIdCard(string $value): bool
    {
        // 定义身份证号码的正则表达式
        $pattern = '/^[1-9]\d{5}(((19|20)\d{2}(0[13578]|1[02])(0[1-9]|[12]\d|3[01]))|((19|20)\d{2}(0[469]|11)(0[1-9]|[12]\d|30))|(19|20)\d{2}02(0[1-9]|1\d|2[0-8]))\d{3}[\dxX]$/';
        // 验证身份证号码的格式是否正确
        if (!preg_match($pattern, $value)) {
            return false;
        }

        // 提取身份证号码的前17位
        $idcard_base = substr($value, 0, 17);
        // 提取校验位
        $verify_code = substr($value, 17, 1);
        // 将小写的 'x' 转换为大写的 'X' 或数字 '10'
        if (strtolower($verify_code) == 'x') {
            $verify_code = '10';
        }

        // 定义校验因子
        $factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
        // 定义校验码列表
        $verify_code_list = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

        // 计算校验和
        $total = 0;
        for ($i = 0; $i < 17; $i++) {
            $total += (int)substr($idcard_base, $i, 1) * $factor[$i];
        }

        // 计算模
        $mod = $total % 11;

        // 验证校验位是否正确
        if ($verify_code_list[$mod] == $verify_code) {
            return true;
        } else {
            return false;
        }
    }
}
